/*
 * Copyright 2018 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.j2cl.tools.rta.pruningresults;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableList;
import com.google.j2cl.tools.minifier.J2clMinifier;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Integration tests for the line pruning mechanism. */
@RunWith(JUnit4.class)
public class PruningResultsTest {
  private static class J2clFileInfo {
    private final Path path;
    private final String content;

    private J2clFileInfo(Path path, String content) {
      this.path = path;
      this.content = content;
    }
  }

  private static final String FILE_DIRECTORY = "com/google/j2cl/tools/rta/pruningresults/";

  private static J2clMinifier j2clMinifier;
  private static String j2clOutputDirectory;
  private static J2clFileInfo fooFile;
  private static J2clFileInfo barFile;

  @BeforeClass
  public static void setUp() throws Exception {
    j2clMinifier = new J2clMinifier();
    readJ2clFiles();
  }

  private static void readJ2clFiles() throws IOException {
    j2clOutputDirectory = checkNotNull(System.getProperty("j2cl_output_directory"));
    fooFile = readFile("Foo.impl.java.js");
    barFile = readFile("Bar.impl.java.js");
  }

  private static J2clFileInfo readFile(String fileName) {
    try {
      Path path = createAbsolutePath(fileName);
      String content = Files.readString(path);
      return new J2clFileInfo(path, content);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  @Test
  public void testFooImplFileIsCorrectlyPruned() {
    ImmutableList<String> removedLinesRegexs =
        ImmutableList.of(
            "constructor\\(\\) \\{", "create..\\(\\) \\{", "\\('This is unused'\\);"
            // TODO(b/116175766): getter and setter need to be removed before enabling this check
            // "unusedStaticField"
            );

    ImmutableList<String> notRemovedLinesChecks =
        ImmutableList.of(
            "entryPoint() {",
            "$clinit() {",
            "$loadModules() {");

    // Because the input is generated by j2cl and can change, first assert the content of the file
    // contains the expected lines.
    removedLinesRegexs.forEach(
        regex ->
            assertWithMessage("Input file is incorrect:")
                .that(fooFile.content)
                .containsMatch(regex));
    notRemovedLinesChecks.forEach(
        line -> assertWithMessage("Input file is incorrect:").that(fooFile.content).contains(line));

    // assert that only the correct lines are removed
    String contentAfterLineRemoval = j2clMinifier.minify(fooFile.path.toString(), fooFile.content);
    removedLinesRegexs.forEach(
        regex -> assertThat(contentAfterLineRemoval).doesNotContainMatch(regex));
    notRemovedLinesChecks.forEach(line -> assertThat(contentAfterLineRemoval).contains(line));

    // assert we don't change the number of lines for not breaking source map.
    assertThat(numberOfLinesOf(fooFile.content))
        .isEqualTo(numberOfLinesOf(contentAfterLineRemoval));
  }

  @Test
  public void testBarImplFileIsNotPruned() {
    assertFileContentIsNotPruned(barFile.path, barFile.content);
  }

  @Test
  public void testUnusedTypeFilesArePruned() {
    assertFileIsPruned(createAbsolutePath("UnusedType.impl.java.js"));
    assertFileIsPruned(createAbsolutePath("UnusedType.java.js"));
  }

  @Test
  public void testHeaderFilesAreNotPruned() {
    // We don't care about the real content of the file as it should not be modified. By passing
    // a one line content, the test will  either throw an exception if J2CLPruner try to remove a
    // line with an index > 0 or fail if J2clPruner removes the first line or the entire file.
    // Otherwise it will success as expected.
    String fakeContent = "fake";

    assertFileContentIsNotPruned(createAbsolutePath("Foo.java.js"), fakeContent);
    assertFileContentIsNotPruned(createAbsolutePath("Bar.java.js"), fakeContent);
  }

  private void assertFileIsPruned(Path filePath) {
    String content = "Fake file content";
    assertWithMessage("Unused file [%s] has not been pruned.", filePath)
        .that(j2clMinifier.minify(filePath.toString(), content))
        .isEmpty();
  }

  private void assertFileContentIsNotPruned(Path filePath, String fileContent) {
    String minifiedAndPruned = j2clMinifier.minify(filePath.toString(), fileContent);
    String onlyMinified = j2clMinifier.minify(fileContent);

    assertWithMessage("File [%s] has been pruned.", filePath)
        .that(minifiedAndPruned)
        .isEqualTo(onlyMinified);
  }

  private static Path createAbsolutePath(String fileName) {
    return Path.of(j2clOutputDirectory, FILE_DIRECTORY, fileName);
  }

  private static int numberOfLinesOf(String content) {
    return CharMatcher.is('\n').countIn(content);
  }
}
